package com.log.service;

import java.lang.reflect.Modifier;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang3.Validate;
import org.reflections.Reflections;
import org.reflections.scanners.SubTypesScanner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Service;

import com.log.annotation.LogCollection;
import com.log.template.BaseLogBean;
import com.log.template.LogCollectionType;
import com.log.template.LogPkg;

/**
 * @author ZGame 初级版本 数据库日志服务类 分布式日志记录,不同的服务器会把日志记录在一起 主要记录需要统计或者非常重要的日志 记录了数据库日志,必须再次记录到本地日志当中,已做备份记录
 */
@Service
public class GameLogService {
    
    public static final Logger LOGGER = LoggerFactory.getLogger(GameLogService.class);//
    
    @Autowired
    private MongoTemplate logMongoTemplate;// 连接对象
    
    @Autowired
    private LogPkg logPkg;
    
    private ThreadPoolExecutor defaultExecutor;
    
    private ThreadPoolExecutor handlerExecutor;
    
    private Timer timer = new Timer("GameLogService-UpdateTimer");
    
    private BlockingQueue<Runnable> workerQueue = new LinkedBlockingQueue<Runnable>();
    
    private BlockingQueue<Runnable> handlerQueue = new LinkedBlockingQueue<Runnable>();
    
    private Map<String, LogCollectionType> collectionTypeMap = new HashMap<>();
    
    private Map<String, String> localLoggerMap = new HashMap<>();
    
    private ClassPathXmlApplicationContext context;
    
    private List<BaseLogBean> logs = new ArrayList<BaseLogBean>();
    
    private Object staticHelper = new Object();
    
    private boolean stopped;
    
    private long totalRecord = 0;
    
    private long lastCheckTime;
    
    protected static final SimpleDateFormat YYYY_MM_DD = new SimpleDateFormat("yyyyMMdd");
    
    protected static final SimpleDateFormat YYYY_MM_WW = new SimpleDateFormat("yyyyMMWW");
    
    protected static final SimpleDateFormat YYYY_MM = new SimpleDateFormat("yyyyMM");
    
    public void start() {
        Reflections refs = new Reflections(logPkg.getScanPkg(), new SubTypesScanner(false));
        for (Class<? extends BaseLogBean> clazz : refs.getSubTypesOf(BaseLogBean.class)) {
            if (Modifier.isAbstract(clazz.getModifiers())) {
                continue;
            }
            LogCollection table = Validate.notNull(clazz.getAnnotation(LogCollection.class),
                    String.format("%s 必须继承 %s。", clazz.getName(), LogCollection.class.getName()));
            collectionTypeMap.put(clazz.getSimpleName().toLowerCase(), table.collectionType());
            localLoggerMap.put(clazz.getSimpleName().toLowerCase(), table.localLogger());
        }
        defaultExecutor = new ThreadPoolExecutor(1, logPkg.getMaximumPoolSize(), 0, TimeUnit.MILLISECONDS, workerQueue,
                r -> {
                    Thread thread = new Thread(r);
                    thread.setName("GameLogService-Default");
                    thread.setUncaughtExceptionHandler((t, e) -> {
                        LOGGER.error("捕获未处理的异常！", e);
                    });
                    return thread;
                });
        handlerExecutor = new ThreadPoolExecutor(1, 1, 0, TimeUnit.MILLISECONDS, handlerQueue, r -> {
            Thread thread = new Thread(r);
            thread.setName("GameLogServer-Handler");
            thread.setUncaughtExceptionHandler((t, e) -> {
                LOGGER.error("捕获未处理的异常！", e);
            });
            return thread;
        });
    }
    
    public void stop() {
        synchronized (staticHelper) {
            stopped = true;
        }
        if (timer != null) {
            timer.cancel();
        }
        while (!logs.isEmpty()) {
            LOGGER.info("尚有'{}'条日志未保存！" + logs.size());
        }
        context.close();
    }
    
    public void dbExcute(BaseLogBean log) {
        try {
            defaultExecutor.execute(() -> {
                
                logMongoTemplate.collectionExists(buildCollectionName(log));
                // 数据库记录
                logMongoTemplate.save(log, buildCollectionName(log));
                
                // 本地记录
                String name = log.getClass().getSimpleName().toLowerCase();
                String loggerName = localLoggerMap.get(name);
                if (loggerName == null) {
                    LOGGER.error("没有本地日志logger  name:" + name);
                } else {
                    LoggerFactory.getLogger(loggerName).info(log.toJson());
                }
            });
        } catch (Exception e) {
            LOGGER.error("日志服务器捕获到未处理的异常！", e);
        }
    }
    
    protected String buildCollectionName(BaseLogBean log) {
        String name = log.getClass().getSimpleName().toLowerCase();
        long time = log.getDealTime() * 1000l;
        LogCollectionType type = collectionTypeMap.get(name);
        switch (type) {
            case Day: {
                return String.format("%s_%s_%s", name, type.getType(), YYYY_MM_DD.format(new Date(time)));
            }
            case WEEK: {
                return String.format("%s_%s_%s", name, type.getType(), YYYY_MM_WW.format(new Date(time)));
            }
            case Month: {
                return String.format("%s_%s_%s", name, type.getType(), YYYY_MM.format(new Date(time)));
            }
            default: {
                return String.format("%s_%s", name, type.name().toLowerCase());
            }
        }
    }
    
    public boolean isSameDay(long time, long time2) {
        Calendar instance = Calendar.getInstance();
        instance.setTimeInMillis(time);
        int d1 = instance.get(Calendar.DAY_OF_YEAR);
        int y1 = instance.get(Calendar.YEAR);
        instance.setTimeInMillis(time2);
        int d2 = instance.get(Calendar.DAY_OF_YEAR);
        int y2 = instance.get(Calendar.YEAR);
        return y1 == y2 && d1 == d2;
    }
    
    public int duringDay(long time, long time2) {
        Calendar instance = Calendar.getInstance();
        instance.setTimeInMillis(time);
        int d1 = instance.get(Calendar.DAY_OF_YEAR);
        int y1 = instance.get(Calendar.YEAR);
        instance.setTimeInMillis(time2);
        int d2 = instance.get(Calendar.DAY_OF_YEAR);
        int y2 = instance.get(Calendar.YEAR);
        int duringYear = Math.abs(y1 - y2);
        int duringDay = Math.abs(d1 - d2);
        if (duringYear == 0) {
            return duringDay;
        } else if (duringYear >= 1) {
            if (y1 > y2) {
                return y1 + 365 + d1 - d2;
            } else {
                return y2 + 365 + d2 - d1;
            }
        }
        return 0;
    }
    
    public int duringMonth(long time, long time2) {
        Calendar instance = Calendar.getInstance();
        instance.setTimeInMillis(time);
        int m1 = instance.get(Calendar.MONTH);
        int y1 = instance.get(Calendar.YEAR);
        instance.setTimeInMillis(time2);
        int m2 = instance.get(Calendar.MONTH);
        int y2 = instance.get(Calendar.YEAR);
        int duringYear = Math.abs(y1 - y2);
        int duringMonth = Math.abs(m1 - m2);
        if (duringYear == 0) {
            return duringMonth;
        } else if (duringYear >= 1) {
            if (y1 > y2) {
                return y1 + 12 + m1 - m2;
            } else {
                return y2 + 12 + m2 - m1;
            }
        }
        return 0;
    }
    
    public int duringWeek(long time, long time2) {
        Calendar instance = Calendar.getInstance();
        instance.setTimeInMillis(time);
        int w1 = instance.get(Calendar.WEEK_OF_YEAR);
        int y1 = instance.get(Calendar.YEAR);
        instance.setTimeInMillis(time2);
        int w2 = instance.get(Calendar.WEEK_OF_YEAR);
        int y2 = instance.get(Calendar.YEAR);
        int duringYear = Math.abs(y1 - y2);
        int duringWeek = Math.abs(w1 - w2);
        if (duringYear == 0) {
            return duringWeek;
        } else if (duringYear >= 1) {
            if (y1 > y2) {
                return y1 + 52 + w1 - w2;
            } else {
                return y2 + 52 + w2 - w1;
            }
        }
        return 0;
    }
}
